package com.leslie.hhcommunity.service.impl;

import com.github.benmanes.caffeine.cache.CacheLoader;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.leslie.hhcommunity.dao.DiscussPostMapper;
import com.leslie.hhcommunity.entity.DiscussPost;
import com.leslie.hhcommunity.service.DiscussPostService;
import com.leslie.hhcommunity.util.RedisKeyUtil;
import com.leslie.hhcommunity.util.SensitiveFilter;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.util.HtmlUtils;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * @author
 * @PackageName:com.leslie.hhcommunity.service.impl
 * @ClassnName:DiscussServiceImpl
 * @Description:
 * @Date 2021/12/31  17:47
 */
@Service
public class DiscussPostServiceImpl implements DiscussPostService {

	@Autowired
	DiscussPostMapper discussPostMapper;

	@Autowired
	SensitiveFilter sensitiveFilter;

	@Autowired
	RedisTemplate redisTemplate;

	@Value("${caffeine.posts.max-size}")
	private int maxSize;
	@Value("${caffeine.posts.expire-seconds}")
	private int expireSeconds;

	private static final Logger logger = LoggerFactory.getLogger(DiscussPostService.class);

	// Caffeine核心接口：Cache, LoadingCache, AsyncLoadingCache
	// 帖子列表缓存
	private LoadingCache<String, List<DiscussPost>> postListCache;
	// 帖子总数缓存
	private LoadingCache<Integer, Integer> postRowsCache;

	//@PostConstruct 在服务器加载Servlet的时候运行，并且只会被服务器执行一次
	@PostConstruct
	public void init() {
		// 初始化帖子列表缓存
		postListCache = Caffeine.newBuilder()
				.maximumSize(maxSize)
				.expireAfterWrite(expireSeconds, TimeUnit.SECONDS)
				.build(new CacheLoader<String, List<DiscussPost>>() {
					@Nullable
					@Override
					public List<DiscussPost> load(@NonNull String key) throws Exception {
						if (key == null || key.length() == 0) {
							throw new IllegalArgumentException("参数错误!");
						}

						String[] params = key.split(":");
						if (params == null || params.length != 2) {
							throw new IllegalArgumentException("参数错误!");
						}

						int offset = Integer.valueOf(params[0]);
						int limit = Integer.valueOf(params[1]);

//						logger.debug("load post list from DB.");
//						return discussPostMapper.selectDiscussPosts(0, offset, limit, 1);

						// 二级缓存: Redis -> mysql
						String redisKey = RedisKeyUtil.getHotPostListKey(offset, limit);
						// 如果redis中不存在就去数据库里查，查到了就更新到redis中，然后返回作为本地缓存的同步
						if (!redisTemplate.hasKey(redisKey)) {
							logger.debug("redis中没有缓存");
							logger.debug("load post list from DB.");
							List<DiscussPost> result = discussPostMapper.selectDiscussPosts(0, offset, limit, 1);
							for (DiscussPost post : result) {
								redisTemplate.opsForZSet().add(redisKey, post, post.getScore());
							}
							redisTemplate.expire(redisKey, 15, TimeUnit.SECONDS);
							return result;
						} else {
							logger.debug("redis中有缓存");
							// 要查看redis数据是否过期
							System.out.println(redisTemplate.getExpire(redisKey));
							boolean isExpired = redisTemplate.getExpire(redisKey) < 0 ? true : false;
							if (isExpired) {
								// 如果过期的话，就删去key，就去数据库里查
								logger.debug("redis expired!");
								redisTemplate.delete(redisKey);
								logger.debug("load post list from DB.");
								List<DiscussPost> result = discussPostMapper.selectDiscussPosts(0, offset, limit, 1);
								for (DiscussPost post : result) {
									redisTemplate.opsForZSet().add(redisKey, post, post.getScore());
								}
								redisTemplate.expire(redisKey, 7 * 60, TimeUnit.SECONDS);
								return result;
							} else {
								Set<DiscussPost> posts = redisTemplate.opsForZSet().reverseRange(redisKey, offset, limit);
								logger.debug("redis len: " + redisTemplate.opsForZSet().zCard(redisKey));
								logger.debug("load post list from redis.");
								List<DiscussPost> list = new ArrayList<>();
								for (DiscussPost post : posts) {
									list.add(post);
								}
								return list;
							}
						}
					}
				});

		// 初始化帖子总数缓存
		postRowsCache = Caffeine.newBuilder()
				.maximumSize(maxSize)
				.expireAfterWrite(expireSeconds, TimeUnit.SECONDS)
				.build(new CacheLoader<Integer, Integer>() {
					@Nullable
					@Override
					public Integer load(@NonNull Integer key) throws Exception {
						logger.debug("load post rows from DB.");
						return discussPostMapper.selectDiscussPostRows(key);
					}
				});
	}

	/**
	 * 分页查询帖子
	 *
	 * @param userId
	 * @param offset
	 * @param limit
	 * @return
	 */
	@Override
	public List<DiscussPost> selectDiscussPosts(int userId, int offset, int limit, int orderMode) {
		if (userId == 0 && orderMode == 1) {
			return postListCache.get(offset + ":" + limit);
		}

		logger.debug("load post list from DB.");

		List<DiscussPost> discussPosts = discussPostMapper.selectDiscussPosts(userId, offset, limit, orderMode);
		return discussPosts;
	}

	/**
	 * 查询用户帖子数量
	 *
	 * @param userId
	 * @return
	 */
	@Override
	public int selectDiscussPostRows(int userId) {
		if (userId == 0) {
			return postRowsCache.get(userId);
		}

		logger.debug("load post rows from DB.");

		int rows = discussPostMapper.selectDiscussPostRows(userId);
		return rows;
	}

	/**
	 * 写帖子
	 *
	 * @param post
	 * @return
	 */
	@Override
	public int insertDiscussPost(DiscussPost post) {
		if (post == null) {
			throw new IllegalArgumentException("参数 不能为空");
		}
		//转义HTML标记,防止HTML代码注入导致XSS攻击
		post.setTitle(HtmlUtils.htmlEscape(post.getTitle()));
		post.setContent(HtmlUtils.htmlEscape(post.getContent()));
		//过滤敏感词
		post.setTitle(sensitiveFilter.filter(post.getTitle()));
		post.setContent(sensitiveFilter.filter(post.getContent()));
		return discussPostMapper.insertDiscussPost(post);
	}

	@Override
	public DiscussPost selectDiscussPostById(int id) {
		return discussPostMapper.selectDiscussPostById(id);
	}

	/**
	 * 更新帖子评论数量
	 *
	 * @param id
	 * @param commentCount
	 * @return
	 */
	@Override
	public int updateCommentCount(int id, int commentCount) {
		return discussPostMapper.updateCommentCount(id, commentCount);
	}

	/**
	 * 更新帖子类型 0：普通 1：置顶
	 *
	 * @param id
	 * @param type
	 * @return
	 */
	@Override
	public int updateType(int id, int type) {
		return discussPostMapper.updateType(id, type);
	}

	/**
	 * 更新帖子状态 0：正常 1：精华 2：拉黑
	 *
	 * @param id
	 * @param status
	 * @return
	 */
	@Override
	public int updateStatus(int id, int status) {
		return discussPostMapper.updateStatus(id, status);
	}

	/**
	 * 更新帖子分数
	 *
	 * @param postId
	 * @param score
	 */
	@Override
	public int updateScore(int postId, double score) {
		return discussPostMapper.updateScore(postId, score);
	}
}
